Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PostPreviewButton: rewrite to functional, avoid state transitions in lifecycles #44971

Merged
merged 9 commits into from
Jul 10, 2023

Conversation

jsnajdr
Copy link
Member

@jsnajdr jsnajdr commented Oct 14, 2022

This PR extracts one part of the React 18 migration from #32765, the one where PostPreviewButton is rewritten to a functional component and awaits save/autosave instead of setting the previewWindow.location in a componentDidUpdate lifecycle method.

The second commit ensures that the preview dropdown menu is closed after clicking the "preview in another tab" item. That's done with a new onPreview prop for PostPreviewButton, and passing the onClose render prop from DropDownMenu down through PreviewOptions to the button component.

In current state, the PR has one major problem that needs to be addressed before it can be merged: if the post has active metaboxes, we need to wait for them to be finished saving before we can show the preview. But that waiting is now broken.

Originally, the componentDidUpdate lifecycle waited for the previewLink prop to change from null to a valid URL, and then set the preview location. With the forcePreviewLink prop, which is being set to null when isSavingMetaboxes() is true, we were keeping the null value for a while after the post save was finished, but that doesn't work now.

Ideally, the "save metaboxes" action should hook into the savePost and autosave actions and be awaited there. That would replace the current listener that tries to catch the "save finished" event by checking isSavingPost transitions from true to false.

@jsnajdr jsnajdr requested a review from youknowriad October 14, 2022 09:22
@jsnajdr jsnajdr requested a review from ellatrix as a code owner October 14, 2022 09:22
@jsnajdr jsnajdr self-assigned this Oct 14, 2022
@github-actions
Copy link

github-actions bot commented Oct 14, 2022

Size Change: -241 B (0%)

Total Size: 1.43 MB

Filename Size Change
build/block-editor/index.min.js 209 kB +2 B (0%)
build/edit-post/index.min.js 35.4 kB -66 B (0%)
build/edit-site/index.min.js 87.6 kB +2 B (0%)
build/editor/index.min.js 45.2 kB -179 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 955 B
build/annotations/index.min.js 2.69 kB
build/api-fetch/index.min.js 2.28 kB
build/autop/index.min.js 2.1 kB
build/blob/index.min.js 451 B
build/block-directory/index.min.js 6.99 kB
build/block-directory/style-rtl.css 1.02 kB
build/block-directory/style.css 1.02 kB
build/block-editor/content-rtl.css 4.26 kB
build/block-editor/content.css 4.25 kB
build/block-editor/default-editor-styles-rtl.css 381 B
build/block-editor/default-editor-styles.css 381 B
build/block-editor/style-rtl.css 14.8 kB
build/block-editor/style.css 14.8 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 122 B
build/block-library/blocks/audio/style.css 122 B
build/block-library/blocks/audio/theme-rtl.css 126 B
build/block-library/blocks/audio/theme.css 126 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/block/editor-rtl.css 305 B
build/block-library/blocks/block/editor.css 305 B
build/block-library/blocks/button/editor-rtl.css 584 B
build/block-library/blocks/button/editor.css 582 B
build/block-library/blocks/button/style-rtl.css 624 B
build/block-library/blocks/button/style.css 623 B
build/block-library/blocks/buttons/editor-rtl.css 337 B
build/block-library/blocks/buttons/editor.css 337 B
build/block-library/blocks/buttons/style-rtl.css 332 B
build/block-library/blocks/buttons/style.css 332 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 113 B
build/block-library/blocks/categories/editor.css 112 B
build/block-library/blocks/categories/style-rtl.css 124 B
build/block-library/blocks/categories/style.css 124 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 121 B
build/block-library/blocks/code/style.css 121 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 409 B
build/block-library/blocks/columns/style.css 409 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 199 B
build/block-library/blocks/comment-template/style.css 198 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 840 B
build/block-library/blocks/comments/editor.css 839 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 636 B
build/block-library/blocks/cover/editor-rtl.css 647 B
build/block-library/blocks/cover/editor.css 650 B
build/block-library/blocks/cover/style-rtl.css 1.61 kB
build/block-library/blocks/cover/style.css 1.6 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 178 B
build/block-library/blocks/details/style.css 178 B
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 126 B
build/block-library/blocks/embed/theme.css 126 B
build/block-library/blocks/file/editor-rtl.css 316 B
build/block-library/blocks/file/editor.css 316 B
build/block-library/blocks/file/style-rtl.css 269 B
build/block-library/blocks/file/style.css 270 B
build/block-library/blocks/file/view.min.js 312 B
build/block-library/blocks/footnotes/style-rtl.css 200 B
build/block-library/blocks/footnotes/style.css 199 B
build/block-library/blocks/freeform/editor-rtl.css 2.58 kB
build/block-library/blocks/freeform/editor.css 2.58 kB
build/block-library/blocks/gallery/editor-rtl.css 947 B
build/block-library/blocks/gallery/editor.css 952 B
build/block-library/blocks/gallery/style-rtl.css 1.53 kB
build/block-library/blocks/gallery/style.css 1.53 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 654 B
build/block-library/blocks/group/editor.css 654 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 336 B
build/block-library/blocks/html/editor.css 337 B
build/block-library/blocks/image/editor-rtl.css 834 B
build/block-library/blocks/image/editor.css 833 B
build/block-library/blocks/image/style-rtl.css 1.42 kB
build/block-library/blocks/image/style.css 1.42 kB
build/block-library/blocks/image/theme-rtl.css 126 B
build/block-library/blocks/image/theme.css 126 B
build/block-library/blocks/image/view-interactivity.min.js 1.46 kB
build/block-library/blocks/latest-comments/style-rtl.css 357 B
build/block-library/blocks/latest-comments/style.css 357 B
build/block-library/blocks/latest-posts/editor-rtl.css 213 B
build/block-library/blocks/latest-posts/editor.css 212 B
build/block-library/blocks/latest-posts/style-rtl.css 478 B
build/block-library/blocks/latest-posts/style.css 478 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 507 B
build/block-library/blocks/media-text/style.css 505 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 712 B
build/block-library/blocks/navigation-link/editor.css 711 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 296 B
build/block-library/blocks/navigation-submenu/editor.css 295 B
build/block-library/blocks/navigation/editor-rtl.css 2.26 kB
build/block-library/blocks/navigation/editor.css 2.26 kB
build/block-library/blocks/navigation/style-rtl.css 2.21 kB
build/block-library/blocks/navigation/style.css 2.2 kB
build/block-library/blocks/navigation/view.min.js 984 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 401 B
build/block-library/blocks/page-list/editor.css 401 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 174 B
build/block-library/blocks/paragraph/editor.css 174 B
build/block-library/blocks/paragraph/style-rtl.css 279 B
build/block-library/blocks/paragraph/style.css 281 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 508 B
build/block-library/blocks/post-comments-form/style.css 508 B
build/block-library/blocks/post-date/style-rtl.css 61 B
build/block-library/blocks/post-date/style.css 61 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 141 B
build/block-library/blocks/post-excerpt/style.css 141 B
build/block-library/blocks/post-featured-image/editor-rtl.css 588 B
build/block-library/blocks/post-featured-image/editor.css 586 B
build/block-library/blocks/post-featured-image/style-rtl.css 319 B
build/block-library/blocks/post-featured-image/style.css 319 B
build/block-library/blocks/post-navigation-link/style-rtl.css 153 B
build/block-library/blocks/post-navigation-link/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 314 B
build/block-library/blocks/post-template/style.css 314 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 69 B
build/block-library/blocks/post-time-to-read/style.css 69 B
build/block-library/blocks/post-title/style-rtl.css 100 B
build/block-library/blocks/post-title/style.css 100 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 335 B
build/block-library/blocks/pullquote/style.css 335 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 302 B
build/block-library/blocks/query-pagination/style.css 299 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/query/editor-rtl.css 450 B
build/block-library/blocks/query/editor.css 449 B
build/block-library/blocks/quote/style-rtl.css 222 B
build/block-library/blocks/quote/style.css 222 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 149 B
build/block-library/blocks/rss/editor.css 149 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 178 B
build/block-library/blocks/search/editor.css 178 B
build/block-library/blocks/search/style-rtl.css 587 B
build/block-library/blocks/search/style.css 584 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/search/view.min.js 531 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 234 B
build/block-library/blocks/separator/style.css 234 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 323 B
build/block-library/blocks/shortcode/editor.css 323 B
build/block-library/blocks/site-logo/editor-rtl.css 754 B
build/block-library/blocks/site-logo/editor.css 754 B
build/block-library/blocks/site-logo/style-rtl.css 203 B
build/block-library/blocks/site-logo/style.css 203 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 116 B
build/block-library/blocks/site-title/editor.css 116 B
build/block-library/blocks/site-title/style-rtl.css 57 B
build/block-library/blocks/site-title/style.css 57 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.43 kB
build/block-library/blocks/social-links/style.css 1.42 kB
build/block-library/blocks/spacer/editor-rtl.css 348 B
build/block-library/blocks/spacer/editor.css 348 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 433 B
build/block-library/blocks/table/editor.css 433 B
build/block-library/blocks/table/style-rtl.css 645 B
build/block-library/blocks/table/style.css 644 B
build/block-library/blocks/table/theme-rtl.css 146 B
build/block-library/blocks/table/theme.css 146 B
build/block-library/blocks/tag-cloud/style-rtl.css 251 B
build/block-library/blocks/tag-cloud/style.css 253 B
build/block-library/blocks/template-part/editor-rtl.css 403 B
build/block-library/blocks/template-part/editor.css 403 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/term-description/style-rtl.css 111 B
build/block-library/blocks/term-description/style.css 111 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 99 B
build/block-library/blocks/verse/style.css 99 B
build/block-library/blocks/video/editor-rtl.css 552 B
build/block-library/blocks/video/editor.css 555 B
build/block-library/blocks/video/style-rtl.css 174 B
build/block-library/blocks/video/style.css 174 B
build/block-library/blocks/video/theme-rtl.css 126 B
build/block-library/blocks/video/theme.css 126 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.1 kB
build/block-library/common.css 1.1 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 12.1 kB
build/block-library/editor.css 12.1 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 201 kB
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 13.7 kB
build/block-library/style.css 13.7 kB
build/block-library/theme-rtl.css 686 B
build/block-library/theme.css 691 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 51 kB
build/commands/index.min.js 14.9 kB
build/commands/style-rtl.css 827 B
build/commands/style.css 827 B
build/components/index.min.js 240 kB
build/components/style-rtl.css 11.7 kB
build/components/style.css 11.7 kB
build/compose/index.min.js 12 kB
build/core-commands/index.min.js 2.26 kB
build/core-data/index.min.js 16.1 kB
build/customize-widgets/index.min.js 12 kB
build/customize-widgets/style-rtl.css 1.46 kB
build/customize-widgets/style.css 1.45 kB
build/data-controls/index.min.js 640 B
build/data/index.min.js 8.28 kB
build/date/index.min.js 17.8 kB
build/deprecated/index.min.js 451 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.63 kB
build/edit-post/classic-rtl.css 544 B
build/edit-post/classic.css 545 B
build/edit-post/style-rtl.css 7.58 kB
build/edit-post/style.css 7.57 kB
build/edit-site/style-rtl.css 13 kB
build/edit-site/style.css 13 kB
build/edit-widgets/index.min.js 16.9 kB
build/edit-widgets/style-rtl.css 4.52 kB
build/edit-widgets/style.css 4.52 kB
build/editor/style-rtl.css 3.58 kB
build/editor/style.css 3.58 kB
build/element/index.min.js 4.8 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 7.62 kB
build/format-library/style-rtl.css 554 B
build/format-library/style.css 553 B
build/hooks/index.min.js 1.55 kB
build/html-entities/index.min.js 448 B
build/i18n/index.min.js 3.58 kB
build/interactivity/index.min.js 10.2 kB
build/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.64 kB
build/keycodes/index.min.js 1.84 kB
build/list-reusable-blocks/index.min.js 2.18 kB
build/list-reusable-blocks/style-rtl.css 836 B
build/list-reusable-blocks/style.css 836 B
build/media-utils/index.min.js 2.9 kB
build/notices/index.min.js 948 B
build/plugins/index.min.js 1.77 kB
build/preferences-persistence/index.min.js 1.84 kB
build/preferences/index.min.js 1.24 kB
build/primitives/index.min.js 943 B
build/priority-queue/index.min.js 1.52 kB
build/private-apis/index.min.js 943 B
build/react-i18n/index.min.js 615 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/index.min.js 2.54 kB
build/reusable-blocks/style-rtl.css 243 B
build/reusable-blocks/style.css 243 B
build/rich-text/index.min.js 10.9 kB
build/router/index.min.js 1.77 kB
build/server-side-render/index.min.js 1.94 kB
build/shortcode/index.min.js 1.39 kB
build/style-engine/index.min.js 1.83 kB
build/token-list/index.min.js 582 B
build/url/index.min.js 3.57 kB
build/vendors/inert-polyfill.min.js 2.48 kB
build/vendors/react-dom.min.js 41.8 kB
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 958 B
build/warning/index.min.js 268 B
build/widgets/index.min.js 7.16 kB
build/widgets/style-rtl.css 1.15 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.02 kB

compressed-size-action

@youknowriad
Copy link
Contributor

Ideally, the "save metaboxes" action should hook into the savePost and autosave actions and be awaited there. That would replace the current listener that tries to catch the "save finished" event by checking isSavingPost transitions from true to false.

Do you think we can add an unstable action (action in the sense of @wordpress/hooks action in the save action of the editor package or something like that to address that?

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 14, 2022

Do you think we can add an unstable action

Yes, I will work on that 👍 It's quite a detour from the original React 18 migration, but I believe it will improve the post save logic a lot.

We'll probably also need filters for the "is autosaveable?" and "is dirty?" values. Now it's implemented with special like forceIsAutosaveable and forceIsDirty props, which have a hasActiveMetaboxes value.

@youknowriad
Copy link
Contributor

We'll probably also need filters for the "is autosaveable?" and "is dirty?" values. Now it's implemented with special like forceIsAutosaveable and forceIsDirty props, which have a hasActiveMetaboxes value.

Yeah I guess, even if I don't like it much 😬 (a filter in a selector might not be the greatest idea as it doesn't retrigger the subscription on change). My ultimate goal is to actually remove the "editor" package entirely as now, it just became a subset of edit-post and this would remove these hacks but let's leave that for a far future.

@jsnajdr jsnajdr force-pushed the update/post-preview-button branch from ff23f1e to 6071a5a Compare October 19, 2022 11:49
@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 19, 2022

Hi @youknowriad 👋 I implemented the editor.SavePost hook in this PR and migrated the requestMetaBoxUpdates call to use it.

I was able to remove all the forceIsSaving and forcePreviewLink props, because the isSavingPost selector now returns true during the entire save process, including hooks, not only during the entity save. No additional information is needed.

Removing the remaining forceIsAutosaveable and forceIsDirty will be harder, I left them unchanged for now.

I'm not sure how to name the editor.SavePost hook. Maybe editor.__unstableSavePost? Also I don't know which parameters are the right one. Currently there is the previous promise, allowing us to chain hook callback asynchronously after each other and to jump out of the hook chain in case of error. And the options object.

By the way, to test the metaboxes I installed the ACF plugin, and noticed they override the editor.savePost action to add some custom validation code:

Screenshot 2022-10-19 at 13 06 27

That would be a good use case for an editor.preSavePost hook, one that runs before the entity save. Also, their code has a little bug where the .catch at the end swallows errors from the original savePost action, instead of letting them propagate. I'm wondering what's the best way to report that to the plugin authors?

@lgladdy
Copy link

lgladdy commented Oct 19, 2022

@jsnajdr Hey! One of the ACF Devs here. Happened to stumble onto this as part of our report in #44716. Can we just drop our .catch entirely here? I'll dig deeper into this and get it raised as a bug for us to fix.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 20, 2022

Can we just drop our .catch entirely here?

Hi @lgladdy 👋 It's great that we connected so quickly 🙂 Some catch handler will still need to be there, but placed slightly differently:

editor.savePost = function() {
  return acf.validateFormPromise().then(
    () => {
      return originalSavePost();
    },
    ( err ) => {
      // ignore validation error
    }
  );
}

This code has the following behavior:

  • if the form validation succeeds, original savePost will be called, and the new savePost will return its result promise without any handling: if there is an error thrown from original savePost, it will be passed to Gutenberg to handle. The current ACF code fails to pass the error: it will swallow it.
  • if the form validation fails, we're catching and ignoring the validation error. But only the validation error, not an original savePost error! The editor.savePost will return success. We want to do that because acf.validateFormPromise does its own error handling, calling createErrorNotice, resetting the status... If we didn't swallow the validation error, it would be passed to Gutenberg code which would either show a second error notice or not handle the error at all, causing a runtime error.

@jsnajdr jsnajdr force-pushed the update/post-preview-button branch from 1b8dcb3 to a433283 Compare October 20, 2022 11:19
@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 21, 2022

I fixed some unit tests:

  • the isSavingPost started looking at the editor store internal flag rather than the entity's "is saving" flag, because we're now saving more than just the entity. The isSavingPost and isEditedPostAutosaveable tests needed to be updated.
  • the PostPreviewButton unit tests needed to be changed because we started to use useSelect internally, and newly we need to mock the return values of selectors.

Everything is green now and ready for final review. The main thing we need to decide on is the exact name of the hook and its parameters.

.select( coreStore )
.getLastEntitySaveError(
'postType',
previousRecord.type,
previousRecord.id
);

if ( ! error ) {
await applyFilters(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks more like an "action" than a filter, the only reason it's a filter is because we want it to be async. This makes me wonder if we shouldn't just support "async actions" instead.

await doAction( 'editor.SavePost' )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, webpack also distinguishes between sync and async hooks, and it's a good thing to have in a JavaScript project.

We could introduce a new doAsyncAction API which is similar to doAction but awaits between calls and aborts on a rejected promise. The addAction API could stay the same. The entire difference is in how actions are called, not how they are registered.

If the caller makes a mistake and calls doAction instead of doAsyncAction, we could issue a warning in case an action callback returns a promise (or thenable in general) that would be ignored by doAction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious why this needs to be async though, is the thought that options might get changed? if so pass that to the filter, if not just use action without async?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's async because saving post is in principle async. It involves a REST request for saving the post, and we wait for the response etc. And the callbacks registered with the __unstableSavePost hook typically want to do some additional async work related to saving. For example, the edit-post package registers a callback for saving metaboxes. I.e., in addition to the standard post saving, it will go through metabox forms on the page, and save their fields with a separate POST request to the server.


if ( ! error ) {
await applyFilters(
'editor.SavePost',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other question is whether we want this to be stable or internal. Given the questioning about hook vs filter... I might think we should make it unstable first. WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with making it unstable and I'm mainly concerned about what are the right parameters for the action. is editor.__unstableSavePost a good name? Is there prior art for unstable/experimental actions and filters?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

editor.__unstableSavePost a good name

works for me.

Is there prior art for unstable/experimental actions and filters?

I don't know :) (At least, personally I don't remember any)

arguments

We can start with none, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, renamed the action to editor.__unstableSavePost.

We can start with none, right?

I'm starting with options because I need to read options.isAutosave 🙂 This particular action doesn't need anything else, not even basic stuff like the post ID or the post edits.

@jsnajdr jsnajdr force-pushed the update/post-preview-button branch 2 times, most recently from f2c7b36 to 5f9d573 Compare October 24, 2022 12:04
);
} );
export function isSavingPost( state ) {
return !! state.saving.pending;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My ultimate goal (probably never happens but who knows) is to make the editor package obsolete (because as an abstraction it's not clear). The change here goes in the opposite direction because it uses the store of this package. I think it's fine for now but I just wanted to share some thought on this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not just isSavingPost, but also adding a new functionality (the action hook) to the editor.savePost action goes in the opposite direction, too. When the savePost action moves somewhere else (to edit-post?), the isSavingPost selector and the state it reads will move as well.

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything missing here? This looks good in my testing.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 24, 2022

Anything missing here? This looks good in my testing.

As far as I know nothing is missing and once the tests come back green, the PR can be merged.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 24, 2022

A very relevant e2e test is failing right now:

PostPublishButton › should be disabled when metabox is being saved

I'll need to address that before merging.

@jsnajdr jsnajdr force-pushed the update/post-preview-button branch 2 times, most recently from 327edae to 4943da0 Compare December 19, 2022 07:21
@youknowriad
Copy link
Contributor

Looks like this fell through the cracks, I thought it was a nice refactor :) and might help in fixing some preview bugs. Should we try to update this, what was the blocker?

@jsnajdr
Copy link
Member Author

jsnajdr commented Jun 1, 2023

Should we try to update this, what was the blocker?

There is one failing e2e test that relies on the details of the old implementation, and is hard to update. We'll probably have to remove the test.

I didn't forget about this PR, I still think about it every few days 🙂

@Mamaduka
Copy link
Member

Mamaduka commented Jul 4, 2023

@jsnajdr, I created Playwright migration PR for PostPublishButton e2e tests - #52285. Let me know if I can help with failures related to this PR.

@jsnajdr jsnajdr force-pushed the update/post-preview-button branch from 4943da0 to 7376e29 Compare July 10, 2023 09:13
@jsnajdr
Copy link
Member Author

jsnajdr commented Jul 10, 2023

I rebased this old PR onto latest trunk, resolved the conflicts, and removed the "button should be disabled when metabox is being saved" e2e test.

Saving metaboxes, i.e., the requestMetaBoxUpdates action, is now an integral part of the save process, registered as a plugin-like filter. Before this PR, there were actually two semi-parallel save processes, and the PostPreviewButton had to figure out on its own, by evaluating condition like isSaving = isPostSaving || isMetaBoxSaving, whether it should be disabled.

The e2e test was relying on state that is now too internal to be reliably observable by a e2e test.

If the CI checks pass, I believe this can finally be merged.

@jsnajdr jsnajdr merged commit 55fbdd9 into trunk Jul 10, 2023
@jsnajdr jsnajdr deleted the update/post-preview-button branch July 10, 2023 11:20
@github-actions github-actions bot added this to the Gutenberg 16.3 milestone Jul 10, 2023
@jsnajdr
Copy link
Member Author

jsnajdr commented Jul 10, 2023

If this makes its way to 6.3, it will need a dev note about the new editor.__unstableSavePost hook. But it probably won't, 6.3 is in a very late stage of the beta cycle. Also, the hook is named __unstable. We should test it with plugins like ACF before stabilizing it.

@mburridge mburridge added [Type] Code Quality Issues or PRs that relate to code quality [Package] Editor /packages/editor labels Jul 18, 2023
@Mamaduka
Copy link
Member

Mamaduka commented Aug 7, 2023

@jsnajdr, should we add basic documentation for the new hook somewhere? I think that would also help the stabilization of it.

P.S. These two come to mind regarding third-party implementations - #13413 and #49811.

@jsnajdr
Copy link
Member Author

jsnajdr commented Aug 9, 2023

should we add basic documentation for the new hook somewhere?

Yes, I'll document it. The best place seems to be the docs/reference-guides/filters folder.

These two come to mind regarding third-party implementations

I've been chatting with @lgladdy about using the new hook (and adding some new ones, like a BeforeSavePost one) in ACF. That should help figure out what the right inputs and outputs are for the hook, which is the main open question that needs to be answered before stabilizing it.

@Mamaduka
Copy link
Member

Mamaduka commented Aug 9, 2023

Thank youyou, @jsnajdr!

The discussions could also serve for sharing initial documentation and gathering feedback.

Cc-ing @sc0ttkclark, just in case they're also interested in this discussion.

@sc0ttkclark
Copy link

If you get docs or have any discussions about this, I'd love to be a part of that.

@sc0ttkclark
Copy link

I see a few ACF references here, is there a working prototype example of how to leverage what's in this PR beyond documenting it which might take longer? I assume the ACF example will cover the use-cases I'm after as I'm doing something similar for Pods already.

(reposting on my main account, sorry for the confusion)

@jsnajdr
Copy link
Member Author

jsnajdr commented Aug 14, 2023

I see a few ACF references here, is there a working prototype example of how to leverage what's in this PR beyond documenting it which might take longer?

We discussed the specific ACF use case with @lgladdy some time ago, the discussion starts here:
#44971 (comment)

In short, Gutenberg should add also a new preSavePost hook, in addition to the current one which runs after save, and then ACF can use it to run its validation code. Currently, ACF monkey-patches the savePost action, replacing it with a wrapper that first does the ACF validation and then calls the original savePost version.

@lgladdy
Copy link

lgladdy commented Sep 7, 2023

Hi @lgladdy 👋 It's great that we connected so quickly 🙂 Some catch handler will still need to be there, but placed slightly differently:

@jsnajdr Just to note, we shipped a fix for this in ACF 6.2.1 tonight, so hopefully debugging savePost issues with ACF installed will be easier now! Sorry this took so long!

@sc0ttkclark
Copy link

Pre save hook where we could do what ACF seeks to do for validation would be very helpful for other plugins too.

@sc0ttkclark
Copy link

Following up here, did a doc get posted on how to utilize this yet?

@olatechpro
Copy link

Hey @sc0ttkclark I was following this thread as well and after testing the release, it doesn't solve the issue. I believe they only added filter because they needed it for metabox and not for plugins validation consideration.

The filter was added to LINE 190 after the post has already been saved on line 157

So, even though it trigger an error if the filter third party plugin validation failed, it doesn't stop the posts from been published or updated.

It's unfortunate we have to keep waiting for this.

@adamsilverstein
Copy link
Member

In short, Gutenberg should add also a new preSavePost hook, in addition to the current one which runs after save, and then ACF can use it to run its validation code. Currently, ACF monkey-patches the savePost action, replacing it with a wrapper that first does the ACF validation and then calls the original savePost version.

I tried adding that here (feedback welcome!): #58022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Editor /packages/editor [Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants